"""
Booking service for managing meeting room bookings.
"""
from typing import List, Optional
from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from datetime import datetime
from uuid import UUID
from app.models.booking import Booking, BookingStatus
from app.models.room import Room, RoomStatus
from app.models.user import User, UserRole
from app.schemas.booking import BookingCreate, BookingUpdate, CheckAvailabilityResponse
from app.utils.validators import validate_booking_time_range, validate_booking_within_schedule


def get_booking(db: Session, booking_id: UUID) -> Optional[Booking]:
    """Get a booking by ID."""
    return db.query(Booking).filter(Booking.id == booking_id).first()


def get_bookings(
    db: Session,
    skip: int = 0,
    limit: int = 100,
    room_id: Optional[UUID] = None,
    date: Optional[datetime] = None,
    booking_person: Optional[str] = None,
    status: Optional[BookingStatus] = None
) -> List[Booking]:
    """
    Get all bookings with optional filters.
    
    Args:
        db: Database session
        skip: Number of records to skip
        limit: Maximum number of records to return
        room_id: Filter by room ID
        date: Filter by date
        booking_person: Filter by booking person name
        status: Filter by booking status
        
    Returns:
        List of bookings
    """
    query = db.query(Booking)
    
    if room_id:
        query = query.filter(Booking.room_id == room_id)
    
    if date:
        # Filter bookings on the specified date
        start_of_day = date.replace(hour=0, minute=0, second=0, microsecond=0)
        end_of_day = date.replace(hour=23, minute=59, second=59, microsecond=999999)
        query = query.filter(
            Booking.start_time >= start_of_day,
            Booking.start_time <= end_of_day
        )
    
    if booking_person:
        query = query.filter(Booking.booking_person.ilike(f"%{booking_person}%"))
        
    if status:
        query = query.filter(Booking.status == status)
    
    return query.order_by(Booking.start_time.desc()).offset(skip).limit(limit).all()


def check_availability(
    db: Session,
    room_id: UUID,
    start_time: datetime,
    end_time: datetime,
    exclude_booking_id: Optional[UUID] = None
) -> CheckAvailabilityResponse:
    """
    Check if a room is available for booking.
    
    Args:
        db: Database session
        room_id: Room ID to check
        start_time: Proposed start time
        end_time: Proposed end time
        exclude_booking_id: Optional booking ID to exclude (for updates)
        
    Returns:
        CheckAvailabilityResponse with availability status
    """
    # Check if room exists
    room = db.query(Room).filter(Room.id == room_id).first()
    if not room:
        return CheckAvailabilityResponse(
            available=False,
            message="Room not found",
            conflicting_bookings=[]
        )
    
    # Check if room is active (compare enum values directly)
    if room.status.value != RoomStatus.ACTIVE.value:
        return CheckAvailabilityResponse(
            available=False,
            message=f"Room is not available (Status: {room.status.value})",
            conflicting_bookings=[]
        )
    
    # Check within schedule
    is_within_schedule, schedule_msg = validate_booking_within_schedule(
        db, room_id, start_time, end_time
    )
    
    if not is_within_schedule:
        return CheckAvailabilityResponse(
            available=False,
            message=schedule_msg,
            conflicting_bookings=[]
        )
    
    # Check for conflicts
    is_available, conflicts = validate_booking_time_range(
        db, room_id, start_time, end_time, exclude_booking_id
    )
    
    if not is_available:
        conflict_details = [
            {
                "id": str(booking.id),
                "start_time": booking.start_time.isoformat(),
                "end_time": booking.end_time.isoformat(),
                "booking_person": booking.booking_person
            }
            for booking in conflicts
        ]
        
        return CheckAvailabilityResponse(
            available=False,
            message=f"Time slot conflicts with {len(conflicts)} existing booking(s)",
            conflicting_bookings=conflict_details
        )
    
    return CheckAvailabilityResponse(
        available=True,
        message="Time slot is available",
        conflicting_bookings=[]
    )


def create_booking(db: Session, booking: BookingCreate) -> Booking:
    """
    Create a new booking with validation.
    
    Args:
        db: Database session
        booking: Booking creation schema
        
    Returns:
        Created booking object
    """
    # Check if room exists
    room = db.query(Room).filter(Room.id == booking.room_id).first()
    if not room:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Room not found"
        )
    
    # Check if room is active (compare enum values directly)
    if room.status.value != RoomStatus.ACTIVE.value:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Room is not available (Status: {room.status.value})"
        )
    
    # Check availability
    availability = check_availability(
        db, booking.room_id, booking.start_time, booking.end_time
    )
    
    if not availability.available:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=availability.message
        )
    
    # Create booking
    db_booking = Booking(**booking.model_dump())
    db.add(db_booking)
    db.commit()
    db.refresh(db_booking)
    
    return db_booking


def update_booking(
    db: Session,
    booking_id: UUID,
    booking_update: BookingUpdate
) -> Booking:
    """
    Update an existing booking.
    
    Args:
        db: Database session
        booking_id: Booking ID
        booking_update: Booking update schema
        
    Returns:
        Updated booking object
    """
    db_booking = get_booking(db, booking_id)
    if not db_booking:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Booking not found"
        )
    
    update_data = booking_update.model_dump(exclude_unset=True)
    
    # If updating time, check availability
    if "start_time" in update_data or "end_time" in update_data:
        start_time = update_data.get("start_time", db_booking.start_time)
        end_time = update_data.get("end_time", db_booking.end_time)
        
        # Get room_id as string
        room_id_str = str(db_booking.room_id)
        
        availability = check_availability(
            db, UUID(room_id_str), start_time, end_time, exclude_booking_id=booking_id
        )
        
        if not availability.available:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=availability.message
            )
    
    for field, value in update_data.items():
        setattr(db_booking, field, value)
    
    db.commit()
    db.refresh(db_booking)
    
    return db_booking


def delete_booking(
    db: Session,
    booking_id: UUID,
    current_user: User
) -> bool:
    """
    Delete (cancel) a booking.
    
    Args:
        db: Database session
        booking_id: Booking ID
        current_user: Authenticated user requesting the cancellation
        
    Returns:
        True if deleted successfully
    """
    db_booking = get_booking(db, booking_id)
    if not db_booking:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Booking not found"
        )

    is_admin = current_user.role == UserRole.ADMIN
    is_creator = db_booking.booking_person == current_user.username

    if not (is_admin or is_creator):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Not authorized to cancel this booking"
        )

    # Soft delete by setting status to cancelled
    setattr(db_booking, 'status', BookingStatus.CANCELLED)
    db.commit()

    return True

